JavaScript: prototype、__proto__、[[prototype]]的区别

prototype、__proto__、[[prototype]] 三者之间的区别

最近在网上看到 instanceof 的手写版,里面涉及到了 prototype__proto__,仔细推敲 一下代码,发现自己对 prototype, __proto__ 的理解不够深刻导致自己对手写版的 instanceof 代码理解不是很透彻。

首先来看一下 instanceof 的手写代码

function myInstanceof(obj, constructor) {
  	// 拿到obj的
    let implicitPrototype = obj?.__proto__;
    const displayPrototype = constructor.prototype;
    while (implicitPrototype) {
        if (implicitPrototype === displayPrototype) return true;
        implicitPrototype = implicitPrototype.__proto__;
    }
    return false;
}

如果不是很清楚 prototype , __proto__ 的话,很难正确理解上述代码。所以我们先要搞清楚它们的区别。

1. 概念区分

其实说 __proto__ 并不准确,确切的说是对象的 [[prototype]] 属性,只不过在主流的浏览器中,都用 __proto__ 来代表 [[prototype]] 属性,因为 [[prototype]] 只是一个标准,而针对这个标准,不同的浏览器有不同的实现方式。在ES5中用 Object.getPrototypeOf 函数获得一个对象的 [[prototype]]。ES6中,使用 Object.setPrototypeOf 可以直接修改一个对象的 [[prototype]]

prototype 属性是只有函数才特有的属性,当你创建一个函数时,js会自动为这个函数加上 prototype 属性,值是一个空对象。所以,函数在js中是非常特殊的,是所谓的一等公民。

2.必须明确

__proto__ 是隐式原型

prototype 是显式原型

显式原型 :每一个函数在创建之后都会拥有一个名为 prototype 的属性,这个属性指向函数的原型对象。

隐式原型:JavaScript中任意对象都有一个内置属性 [[prototype]],在ES5之前没有标准的方法访问这个内置属性,但是大多数浏览器都支持通过 __proto__ 来访问。

注意: Object.prototype 这个对象是个例外,它的 __proto__ 值为 null

二者的关系:

隐式原型指向创建这个对象的函数(constructor)的 prototype

3.他们的作用

隐式原型的作用:构成原型链,同样用于实现基于原型的继承。举个例子,当我们访问 obj 这个对象中的x属性时,如果在 obj 中找不到,那么就会沿着 __proto__ 依次查找。

显式原型的作用:用来实现基于原型的继承与属性的共享

所以现在我们可以分析一下 instanceof 代码了。一般来说,instanceof 内部实现机制和隐式原型、显式原型有直接的关系。instanceof 的左值一般是一个对象,右值一般是一个构造函数,用来判断左值是否是右值的实例。

所以在我们的手写代码中:

//设 obj instanceof constructor
//通过判断
 obj.__proto__.__proto__ ..... === constructor.prototype
//最终返回true or false

也就是一直沿着obj的 __proto__ 一直寻找下去,知道等于 constructor.prototype 或者 null 为止。

这就是instanceof的原理

4.进一步的探究

console.log(Object instanceof Function); //true
console.log(Function instanceof Object); //true

上面两行代码的结果都为 true,这就有点奇怪了

接下来我们来探究一下:
console.log(Object.prototype.__proto__); //null

所以原型链的尽头(root)是 Object.prototype所有对象均从 Object.prototype 继承属性。

console.log(Function.prototype);
console.log(Function.__proto__);
console.log(Function.prototype === Function.__proto__); //true

所以 Function.prototypeFunction.__proto__同一对象

console.log(Function.prototype === Array.__proto__); //true
console.log(Function.prototype === String.__proto__); //true
console.log(Function.prototype === Object.__proto__); //true
console.log(Function.prototype === Number.__proto__); //true
console.log(Function.prototype === Boolean.__proto__); //true

这意味着: Object/Array/String 等等构造函数本质上和 Function 一样,均继承于 Function.prototype

console.log(Function.prototype.__proto__ === Object.prototype); //true

最后 Function.prototype 直接继承 Object.prototype

总结先有 Object.prototype(原型链顶端), Function.prototype 继承 Object.prototype 而产生,最后,FunctionObject 和其它构造函数继承 Function.prototype 而产生。

------------- 本文结束 感谢阅读 -------------